<?php
//使用模板，生成页面类
//实现功能：
//1、给定模板，读入模板：未给定模板，使用默认模板
//2、替换模板中的内容
//3、输出、生成静态页面
//创建: firmit  2009.10.13
//update: 2010-04-29
//        添加BaseClass的继承
//        修改类中关于错误的显示的部分
//        修改静态页面生的部分的代码，使用filebase类
//	    2012-07-07
//		重构了这个类，参考smarty，只提供了 变量替换/foreach/if/include/include_php
//		以下默认delimiter为{}
//		所有的变量、控制符都要放置在delimiter中
//		对于变量，可是不是使用$符号，但是变量名和delimiter之间不可以有空格
//		其他没有什么限制
//		2012-12-23
//		修正include的文件带有路径时无法正常访问的错误
//		2013-01-22
//		将md5校验的位置放置到文件的末尾
//
//////////////////////////////////////////////////
//    Example:                                  //
//    //初始化，定义相关的变量                  //
//    define ("TMPL", "temp.html");             //
//                                              //
//    //实例化模板类                            //
//    $tmpl = new MC_StaticPageTmpl(模板文件, 缓存地址, 缓存文件名（可选）);      //
//                                              //
//    //加载模板
//    $tmpl->loadTemplete();
//
//    //获取编译后文件的文件名，之后可以使用require加载
//    $tmpl->get_compile_name();
//
//    //获取编译后的文件内容
//    $tmpl->get_complie_file_content();
//////////////////////////////////////////////////

class MC_StaticPageTmpl extends  MC_Base{

	public $left_delimiter ;
	public $right_delimiter ;
	
	var $php_start ;
	var $php_end ;
	
	var $var_match_pattern ;
	var $var_with_dot_match_pattern ;
	var $if_match_pattern ;
	var $elseif_match_pattern ;
	//$elseif_match_pattern = '\s*(else\s+if)(((\s*and|not|or)?\s*(\${1,2}\w+|\d+)\s*(>|<|==|!=|===|>=|<=|<>|!|%)\s*(\${1,2}\w+|\d+)\s*)+)';;
	var $else_match_pattern ;
	var $endif_match_pattern ;
	var $foreach_match_pattern ;
	var $foreachelse_match_pattern ;
	var $endforeach_match_pattern ;
	var $include_match_pattern ;
	var $include_php_match_pattern ;
	
	var $cache_path;
	
	var $templete_file ;
	var $templete_file_content;
	
	var $complied_file;
	var $complied_file_content;
	
	var $prefix_str;
	
	var $var_arr;
	var $var_with_dot_arr;
	var $if_arr;
	var $elseif_arr;
	var $else_arr;
	var $endif_arr;
	var $foreach_arr;
	var $foreachelse_arr;
	var $endforeach_arr;
	var $include_arr;
	var $include_php_arr;
	
	var $_pre_hash_code = "<!--";
	var $_post_hash_code = "-->\n";

	
    /**
     * Model构造函数
     *
     * @param string $_tpfile 模板路径文件名
     * @param string $_cache 缓存目录名
     * @param string $_cache_name 编译好文件名
     * @param string $_prefix_str 在替换的时候的需要前置的内容（仅用于变量名替换）
     * @param string $_ld left delimiter
     * @param string $_rd right delimiter
     */
	function __construct($_tpfile, $_cache, $_cache_name='', $_ld = "{", $_rd = "}", $_prefix_str='' ){
		
		//判断是否是文件
		if(!is_file($_tpfile) || !is_readable($_tpfile)){
			$this->errMsg = "MC_StaticPageTmpl: 模板文件（{$_tpfile}）不存在或不可读";
            throw new MC_Exception($this->errMsg);
			return false;
		}
		$this->templete_file = realpath($_tpfile);
		
		//读取模板文件内容
		$this->templete_file_content = $this->_read_file($this->templete_file);
		if($this->templete_file_content === false){
			$this->errMsg = "MC_StaticPageTmpl: 模板文件（".$this->templete_file."）读取失败";
            throw new MC_Exception($this->errMsg);
			return false;
		}

		//设置cache目录
		if(!is_dir($_cache) || !is_writable($_cache)){
			$this->errMsg = "MC_StaticPageTmpl: 缓存目录（{$_cache}）不存在或不可写入";
            throw new MC_Exception($this->errMsg);
			return false;
		}
		$this->cache_path = realpath($_cache);
		
		//设置编译文件名
		if($_cache_name != '')
			$this->complied_file = basename($_cache_name);
		else
			$this->complied_file = basename($this->templete_file);
			
		$this->complied_file = $this->cache_path."/".$this->complied_file;
		
		$this->prefix_str = $_prefix_str;
		
		$this->left_delimiter = '/'.$_ld;
		$this->right_delimiter = $_rd.'/i';
		
		$this->php_start = "<?php ";
		$this->php_end = " ?>";
		
		//以下使用默认的delimiter，除没有$符的变量匹配，其他的和delimiter间都可以空格
		
		//匹配变量 {var} {$var} {$$var} 
		//替换为 echo $var|$$var
		$this->var_match_pattern = '(\s*\${1,2}\w+?\s*|\w+?)';

		//匹配 {$var1.var2}
		//替换为 $var1[var2]
		$this->var_with_dot_match_pattern = '\s*((\$\w+)(\.\w+)+)\s*';
		
		//匹配 {if $var op xxx}
		//替换为 if($var op xx){
		$this->if_match_pattern = '\s*(if)(((\s*and|not|or|&&|\|\|)?\s*(\${1,2}(\w+|\w+\[.+\]))\s*(>|<|==|!=|===|>=|<=|<>|!|%)\s*(\${1,2}\w+|.+)\s*)+)';
		
		//匹配 {else if (...)}
		//替换为 }else if (...) {
		$this->elseif_match_pattern = '\s*(else)\s'.$this->if_match_pattern;
		//$elseif_match_pattern = '\s*(else\s+if)(((\s*and|not|or)?\s*(\${1,2}\w+|\d+)\s*(>|<|==|!=|===|>=|<=|<>|!|%)\s*(\${1,2}\w+|\d+)\s*)+)';;
		
		//匹配 {else}
		//替换为 }else{
		$this->else_match_pattern = '\s*(else)\s*';
		
		//匹配 {endif}
		//替换为 }
		$this->endif_match_pattern = '\s*((\/|end)if)';
		
		//匹配 {foreach $arr as [$key =>] $value }
		//替换为 if(isarray($arr) && !empty($arr)) { foreach ($arr as [$key =>] $value){
		$this->foreach_match_pattern = '\s*(foreach)\s*((\$(\w+|\w+\[.+\]))\s*as\s*(\$\w+)\s*(=>\s*(\$\w+))?\s*)';
		
		//匹配 {foreachelse}
		//替换为 }else{
		$this->foreachelse_match_pattern = '\s*(foreachelse)\s*';
		
		//匹配 {endforeach}
		//替换为 }
		$this->endforeach_match_pattern = '\s*((\/|end)foreach)';
		
		//匹配 {include = "../../filename"} ([\"|']?)([^ \"'>]+\.($ext))
		//替换为 编译过的file内容
		$this->include_match_pattern = '\s*(include)\s*=\s*["|\']([^ \"\'>]+\.(\w+))["|\']\s*';
		//$this->include_match_pattern = '\s*(include)\s*=\s*["|\'](.+)["|\']\s*';
		
		//匹配 {include_php = "../../filename" }
		//include "../../filename"
		$this->include_php_match_pattern = '\s*(include_php)\s*=\s*["|\']([^ \"\'>]+\.(\w+))["|\']\s*';
		
	}
	
	//获取模板文件内容
	function get_temp_file_content(){
		return $this->templete_file_content;
	}
	
	//获取编译文件内容
	function get_complie_file_content(){
		if($this->complied_file_content == null){
			$this->loadTemplete();
		}
		return $this->complied_file_content;
	}
	
	
	//获取编译文件的文件名
	function get_compile_name(){
		return $this->complied_file;
	}
	
	
	//加载模板
	function loadTemplete(){
		//判断模板文件是否需要重新编译
		if(is_file($this->complied_file)){
			//读取文件内容
			$this->complied_file_content = $this->_read_file($this->complied_file);
			//if($this->complied_file_content !== false){
				//截取md5码
				//$c_md5 = substr($this->complied_file_content, strlen($this->_pre_hash_code), 32);
			//$c_md5 = $this->_get_tail($this->complied_file);
			if($c_md5 = $this->_get_tail($this->complied_file)){
				$c_md5 = substr($c_md5, strlen($this->_pre_hash_code), 32);
				//echo $c_md5;
				//exit();
				//与模板文件比较
				if($c_md5 == md5($this->templete_file_content)){
					//require_once($this->complied_file);
					return true;
				}else{
					//模板有改动
					@unlink($this->complied_file);
				}
				
			}else{
				$this->errMsg = "MC_StaticPageTmpl: 模板(c)文件（".$this->complied_file."）读取失败";
				throw new MC_Exception($this->errMsg);
			}
		}
		//编译版本的文件不存在，就重新编译文件
		if($this->complie_templete() == true ){
			//require_once($this->complied_file);
			return true;
		}else{
			$this->errMsg = "MC_StaticPageTmpl: 模板(c)文件（".$this->complied_file."）加载失败";
			throw new MC_Exception($this->errMsg);
		}
		return false;
	}
	
	//编译模板文件
	function complie_templete(){
		$operat_words = array("var", "var_with_dot", "if", "elseif", "else", "endif", 
							  "foreach", "foreachelse", "endforeach", "include", "include_php");
		
		$this->complied_file_content = $this->templete_file_content;

		//匹配关键字
		$this->match_operate_word($operat_words);
		
		//判断if/endif foreach/endforeach数量是匹配
		if(count($this->if_arr) != count($this->endif_arr)){
			$this->errMsg = "MC_StaticPageTmpl: if 与 endif 关键字数量不匹配";
			throw new MC_Exception($this->errMsg);
			return false;
		}
		
		if(count($this->foreach_arr) != count($this->endforeach_arr)){
			$this->errMsg = "MC_StaticPageTmpl: foreach 与 endforeach 关键字数量不匹配";
			throw new MC_Exception($this->errMsg);
			return false;
		}
					
		//编译
		foreach ($operat_words as $match){
			$method_name = "compile_".$match;
			$match_name = $match."_arr";
			$this->$method_name($this->$match_name, $this->complied_file_content);
		}
		
		$this->complied_file_content .= "\n".$this->_pre_hash_code.md5($this->templete_file_content).$this->_post_hash_code;
		
		if($this->_write_file($this->complied_file, $this->complied_file_content) === false){
			$this->errMsg = "MC_StaticPageTmpl: 模板(c)文件（".$this->complied_file."）写入失败";
			throw new MC_Exception($this->errMsg);
			return false;
		}
		return true;
		
	}
	
	//匹配模板控制符
	function match_operate_word(&$operat_words){
		foreach ($operat_words as $match){
			$match_name = $match."_match_pattern";
			$match_pattern = $this->left_delimiter.$this->$match_name.$this->right_delimiter;
			$match_name = $match."_arr";
			preg_match_all ($match_pattern, $this->complied_file_content, $this->$match_name, PREG_SET_ORDER);
			//fenghao debug
			//echo "<p>$match_pattern </p>\n";
			//var_dump($this->$match_name);
		}
	}
		
		
	//编译变量
	function compile_var(&$var_arr, &$html_code){
		//关键字保留
		$pres_arr = array("if","else","elseif","endif","foreachelse","endforeach");
		foreach($var_arr as $var){
			if(strpos(strtolower($var[1]), '$')!==false){
				$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', ('<?php echo '.$var[1].'; ?>'), $html_code); 
			}else if( !in_array($var[1], $pres_arr) ){
				$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', ('<?php echo $'.$var[1].'; ?>'), $html_code); 
			}
		}
	}

	//编译还有.(dot)的变量
	function compile_var_with_dot(&$var_arr, &$html_code){
		foreach($var_arr as $var){
			//$var1.var2.var3...
			//去掉第一个 $var1.
			$tmp = str_replace(($var[2].'.'), '', $var[1]);
			$tmp = str_replace('.', '][', $tmp);
			$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', ('<?php echo '.$var[2].'['.$tmp.']; ?>'), $html_code); 
		}
	}
	
	//编译if语句
	function compile_if(&$var_arr, &$html_code){
		foreach($var_arr as $var){
			$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', ('<?php '.$var[1].'('.$var[2].'){ ?>'), $html_code); 
		}
	}
	
	//编译elseif
	function compile_elseif(&$var_arr, &$html_code){
		foreach($var_arr as $var){
			$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', ('<?php }'.$var[1].' '.$var[2].'('.$var[3].'){ ?>'), $html_code); 
		}
	}
	
	//编译else
	function compile_else(&$var_arr, &$html_code){
		foreach($var_arr as $var){
			$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', ('<?php }'.$var[1].'{ ?>'), $html_code); 
		}
	}
	
	//编译endif
	function compile_endif(&$var_arr, &$html_code){
		foreach($var_arr as $var){
			$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', ('<?php } ?>'), $html_code); 
		}
	}
	
	//编译foreach
	function compile_foreach(&$var_arr, &$html_code){
		foreach($var_arr as $var){
			$tmp = 'if(is_array('.$var[3].') && !empty('.$var[3].')) ';
			$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', ('<?php '.$tmp.$var[1].'('.$var[2].'){ ?>'), $html_code); 
		}
	}
	
	//编译foreachelse
	function compile_foreachelse(&$var_arr, &$html_code){
		foreach($var_arr as $var){
			$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', ('<?php } else { ?>'), $html_code); 
		}
	}
	
	//编译endforeach
	function compile_endforeach(&$var_arr, &$html_code){
		foreach($var_arr as $var){
			$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', ('<?php } ?>'), $html_code); 
		}
	}

	//编译include
	function compile_include(&$var_arr, &$html_code){
		foreach($var_arr as $var){
			$tmp_path = APPS_DIR.'/'.__get_config('Apps.AppName').'/'.__get_config("HTMLTemp.temp_dir")."/".$var[2];
			$tmpl = new MC_StaticPageTmpl($tmp_path, $this->cache_path, str_replace(array('/','\\'), '_', $var[2]) );
			$tmpl->loadTemplete();
			$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', $tmpl->get_complie_file_content(), $html_code); 
		}
	}

	//编译include_php
	function compile_include_php(&$var_arr, &$html_code){
		foreach($var_arr as $var){
			$html_code = preg_replace ( '/'.preg_quote($var[0], '/').'/', ('<?php require_once "'.$var[2].'"; ?>'), $html_code); 
		}
	}


	private function _read_file($_file){
		if(is_file($_file) && is_readable($_file)){
			return @file_get_contents($_file);
		}else{
			return false;
		}
	}
	
	private function _write_file($_file, &$_content){
		return file_put_contents ( $_file, $_content);
	}
	
	private function _get_tail($_file, $lines=1){
		//ini_set('memory_limit','-1');
		$data = file($_file);
		return $data[count($data)-$lines];
	}

}
?>
